﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using SharpAnalyzer.Entities;
using SharpAnalyzer.Utilities;
using SharpAnalyzer.Collectors;

namespace SharpAnalyzer.Calculators
{
    internal class ProjectMetricsCalculator
    {
        public static Project Calculate(Microsoft.CodeAnalysis.Solution solution, Microsoft.CodeAnalysis.Project project, bool ignoreGeneratedCode)
        {
            Microsoft.CodeAnalysis.Compilation compilation = project.GetCompilationAsync().Result;
            Microsoft.CodeAnalysis.IModuleSymbol moduleSymbol = compilation.Assembly.Modules.FirstOrDefault();

            Namespace[] namespaceMetrics = GetNamespaceDeclarations(project, ignoreGeneratedCode);
            for (int cnt = 0; cnt < namespaceMetrics.Length; cnt++)
            {
                var namespaceMetric = namespaceMetrics[cnt];
                NamespaceMetricsCalculator.Calculate(solution, compilation, ref namespaceMetric);
            }

            // Calculate Internal Metrics
            IEnumerable<string> sourceCC = CalculateClassCoupling(namespaceMetrics);
            Project moduleMetric = new Project();
            moduleMetric.Name = moduleSymbol.Name; // project.AssemblyName;
            moduleMetric.ProjectFile = project.FilePath ?? string.Empty;
            moduleMetric.AssemblyVersion = string.Empty;
            moduleMetric.FileVersion = string.Empty;
            moduleMetric.Namespaces = namespaceMetrics.ToList();
            moduleMetric.Metrics = new List<Metric>()
            {
                new Metric()
                {
                    Name = MetricNames.MaintainabilityIndex,
                    Value = CalculateMaintainabilityIndex(namespaceMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.CyclomaticComplexity,
                    Value = CalculateCyclomaticComplexity(namespaceMetrics),
                },
                new ClassCouplingMetricResult()
                {
                    Name = MetricNames.ClassCoupling,
                    Value = sourceCC.Count(),
                    Types = sourceCC
                },
                new Metric()
                {
                    Name = MetricNames.DepthOfInheritance,
                    Value = CalculateDepthOfInheritance(namespaceMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.LinesOfCode,
                    Value = CalculateLinesOfCode(namespaceMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfNamespaces,
                    Value = namespaceMetrics.Count(),
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfTypes,
                    Value = CalculateNumberOfTypes(namespaceMetrics),
                },
                new Metric()
                {
                    Name = MetricNames.NumberOfMethods,
                    Value = CalculateNumberOfMethods(namespaceMetrics),
                },
            };
            return moduleMetric;
        }

        private static Namespace[] GetNamespaceDeclarations(Microsoft.CodeAnalysis.Project project, bool ignoreGeneratedCode = false)
        {
            List<CodeSectionDeclarationSyntaxInfo> allNamespaces = new List<CodeSectionDeclarationSyntaxInfo>();
            List<string> autoGeneratedCodePatterns = new List<string>()
            {
                ".*\\.g\\.cs$",
                ".*\\.xaml\\.cs$",
                ".*\\.designer\\.cs$"
            };
            foreach (Microsoft.CodeAnalysis.Document document in project.Documents)
            {
                string codeFile = document.FilePath;

                if (ignoreGeneratedCode)
                {
                    // Test all the patterns on a file name and see if it's match for auto generated codes like Form1.designer.cs so we ignore them all
                    if (autoGeneratedCodePatterns.Any(x => Regex.IsMatch(codeFile, x, RegexOptions.IgnoreCase | RegexOptions.Singleline)))
                        continue;
                }
                var namesapces = new NamespaceCollectorSyntaxWalker()
                    .GetNamespaces(document.GetSyntaxRootAsync().Result)
                    .Select(x => new CodeSectionDeclarationSyntaxInfo()
                    {
                        Name = x.GetName(x.SyntaxTree.GetRoot()),
                        CodeFile = codeFile,
                        Syntax = x,
                    });
                allNamespaces.AddRange(namesapces);
            }

            // Group all the namespaces from previous step into by Name
            var retval = allNamespaces
                .GroupBy(x => x.Name)
                .Select(x => new Namespace()
                {
                    Name = x.Key,
                    SyntaxNodes = x.ToList().AsReadOnly(),
                })
                .ToArray();
            return retval;
        }

        #region Accumulate Results
        private static int CalculateNumberOfTypes(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         select namespaceT.Types.Count();
            return source.Sum();
        }

        private static int CalculateNumberOfMethods(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from type in namespaceT.Types
                         select type.Members.Count();
            return source.Sum();
        }

        private static IEnumerable<string> CalculateClassCoupling(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from metric in namespaceT.Metrics.OfType<ClassCouplingMetricResult>()
                         from type in metric.Types
                         orderby type
                         select type;

            return source.Distinct();
        }

        private static double CalculateMaintainabilityIndex(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from metric in namespaceT.Metrics
                         where metric.Name == MetricNames.MaintainabilityIndex
                         select (double)metric.Value;
            if (source.Any())
                return Math.Round(source.Average());
            return 100.0;
        }

        private static int CalculateCyclomaticComplexity(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from metric in namespaceT.Metrics
                         where metric.Name == MetricNames.CyclomaticComplexity
                         select (int)metric.Value;
            return source.Sum();
        }

        private static int CalculateDepthOfInheritance(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from metric in namespaceT.Metrics
                         where metric.Name == MetricNames.DepthOfInheritance
                         select (int)metric.Value;
            if (source.Any())
                return source.Max();

            return 0;
        }

        private static int CalculateLinesOfCode(IEnumerable<Namespace> namspaces)
        {
            var source = from namespaceT in namspaces
                         from metric in namespaceT.Metrics
                         where metric.Name == MetricNames.LinesOfCode
                         select (int)metric.Value;
            return source.Sum();
        }
        #endregion
    }
}